 package com.gmrz.util;
 
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.EnumSet;
 import java.util.Properties;
 import javax.naming.Binding;
 import javax.naming.InitialContext;
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
 
 public class PropertiesSource {

   private Type type;
   private String name;
   private Properties props;
 
   public PropertiesSource(Type type, String name)
   {
     if (type == null) {
       throw new IllegalArgumentException("source type can not be null.");
     }
     if (Strings.isNullOrEmpty(name))
     {
       if (type == Type.CONTEXT)
         name = "java:comp/env";
       else {
         throw new IllegalArgumentException("source name can not be null or empty.");
       }
     }
     this.type = type;
     this.name = name;
   }
 
   public Type getType()
   {
     return this.type;
   }
 
   public String getName()
   {
     return this.name;
   }
 
   public String toString()
   {
     return String.format("%s {type=%s, name=%s}", getClass().getName(), this.type, this.name);
   }
 
   public Properties load(Properties defaults)
     throws IOException
   {
     this.props = new Properties(defaults);
 
     if (this.type == Type.CONTEXT)
       loadFromContext();
     else {
       loadFromStream();
     }
     return this.props;
   }
 
   public Properties load()
     throws IOException
   {
     return load(null);
   }
 
   private void loadFromContext()
     throws IOException
   {
     try
     {
       InitialContext ctx = new InitialContext();
       NamingEnumeration e = ctx.listBindings(this.name);
 
       while (e.hasMore())
       {
         Binding b = (Binding)e.next();
 
         Object o = b.getObject();
         if (o != null)
         {
           this.props.setProperty(b.getName(), o.toString());
         }
       }
     }
     catch (NamingException ex)
     {
       throw new IOException(ex);
     }
   }
 
   private void loadFromStream()
     throws IOException
   {
     InputStream stream = null;
     try
     {
       String streamSource = getStreamSource();
       if (streamSource == null) {
         throw new RuntimeException(this.name + " system property not found.");
       }
       stream = getStream(streamSource);
       if (stream == null) {
         throw new RuntimeException(streamSource + " resource not found.");
       }
       loadFromStream(stream);
     }
     finally
     {
       Streams.close(stream);
     }
   }
 
   private String getStreamSource()
   {
     if (this.type.flags.contains(Flags.PROPERTY))
     {
       return System.getProperty(this.name);
     }
 
     return this.name;
   }
 
   private InputStream getStream(String streamSource)
     throws FileNotFoundException
   {
     if (this.type.flags.contains(Flags.RESOURCE))
     {
       ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
       if (classLoader == null) {
         throw new RuntimeException("ClassLoader is null.");
       }
       return classLoader.getResourceAsStream(streamSource);
     }
 
     return new FileInputStream(streamSource);
   }
 
   private void loadFromStream(InputStream stream)
     throws IOException
   {
     if (this.type.flags.contains(Flags.XML))
     {
       this.props.loadFromXML(stream);
     }
     else
     {
       this.props.load(stream);
     }
   }
 
   public enum Type
   {
     CONTEXT(EnumSet.noneOf(Flags.class)),
 
     FILE_PATH(EnumSet.of(Flags.FILE)),
 
     FILE_PATH_PROPERTY(EnumSet.of(Flags.FILE, Flags.PROPERTY)),
 
     RESOURCE_NAME(EnumSet.of(Flags.FILE, Flags.RESOURCE)),
 
     RESOURCE_NAME_PROPERTY(EnumSet.of(Flags.FILE, Flags.RESOURCE, Flags.PROPERTY)),
 
     XML_FILE_PATH(EnumSet.of(Flags.FILE, Flags.XML)),
 
     XML_FILE_PATH_PROPERTY(EnumSet.of(Flags.FILE, Flags.PROPERTY, Flags.XML)),
 
     XML_RESOURCE_NAME(EnumSet.of(Flags.FILE, Flags.RESOURCE, Flags.XML)),
 
     XML_RESOURCE_NAME_PROPERTY(EnumSet.of(Flags.FILE, Flags.RESOURCE, Flags.PROPERTY, Flags.XML));
 
     private EnumSet<Flags> flags;
 
     Type(EnumSet<Flags> flags)
     {
       this.flags = flags;
     }
   }
 
   private enum Flags
   {
     FILE, 
 
     RESOURCE, 
 
     PROPERTY, 
 
     XML
   }
 }
